feat(fm-backend): extend legacy-metadata self-repair to zellij and cmux backends - #39
Conversation
…ve OAuth auth Current Claude Code (2.1.x) reads its global config from $CLAUDE_CONFIG_DIR/.claude.json (join(CLAUDE_CONFIG_DIR ?? homedir, '.claude.json')) when CLAUDE_CONFIG_DIR is set, not from a .claude.json in the parent dir. The launcher pre-seeded the parent, which CC ignores, so a fresh account still entered the first-run onboarding flow. The onboarding gate is a single key, hasCompletedOnboarding===true. Write the pre-seed (hasCompletedOnboarding + numStartups + per-project hasTrustDialogAccepted) into $CLAUDE_CONFIG_DIR/.claude.json instead. Interactive sessions authenticate from the per-account OAuth credential file $CLAUDE_CONFIG_DIR/.credentials.json (the claudeAiOauth blob from 'claude /login'), not a setup-token / CLAUDE_CODE_OAUTH_TOKEN (env-supplied, non-refreshable, print/CI-only). Clarify the launcher's credential requirement and error path accordingly and document both mechanics. Tests updated to assert the pre-seed lands at the config-dir path CC reads (and not the parent), the onboarding key set, and that the auth refusal steers to OAuth /login rather than a setup-token.
….json) + interactive OAuth auth (robots-4evu)
…m A) A cron ROUTINE is durable, persistent, and inspectable via CronList, so creating/deleting one is not the ephemeral, session-bound, fleet-invisible delegation the guard targets; /schedule relies on CronCreate. Add a third exempt category SCHEDULE_MGMT_TOOLS (croncreate crondelete), wired into the existing allow loop, deliberately narrower than the cron/schedul stems so unknown future cron/schedule tools stay caught. DELEGATION_STEMS unchanged. Tests lock the exemption at exactly two names: CronCreate/CronDelete/CronList allow; ScheduleCreate and the full delegation hazard set (Task, Agent, Workflow, spawn, dispatch, subagent, Worktree, SendMessage, Handoff, Remote) stay denied.
…ble schedule mgmt), test-locked (robots-sy7m)
…eychain (robots-4evu) Adopt the setup-token model (Trillium's direction, 'we use setup token now'): each account authenticates with a ~1yr CLAUDE_CODE_OAUTH_TOKEN (sk-ant-oat01-) from keychain service ccjuggler-acc<N>, honored by current Claude Code interactive sessions — no mid-fleet-run logout, one source of truth with ccjuggler/juggle. Supersedes the interim per-account OAuth .credentials.json approach. Retains the onboarding-skip fix (pre-seed CLAUDE_CONFIG_DIR/.claude.json, where current CC reads it). Tests reconciled: hermetic 'security' keychain mock; assert missing/malformed token refuse loudly and a valid token exports as CLAUDE_CODE_OAUTH_TOKEN.
Mirrors the existing herdr self-repair pattern (PR #20): when endpoint_task_id is absent from legacy meta, re-derive identity from the live backend and self-repair the binding only when it provably still matches, otherwise refuse. - bin/backends/zellij.sh: add fm_backend_zellij_pane_verifies_task, checking the pane's current owning tab against the recorded tab_id and the tab's label via the existing scoped/bare ambiguity-safe fm_backend_zellij_tab_matches_label. - bin/backends/cmux.sh: add fm_backend_cmux_workspace_matches_label (exact-id lookup, never falling back to a label-only search) and fm_backend_cmux_surface_verifies_task composing it with the existing fm_backend_cmux_surface_exists. - bin/fm-backend.sh: wire both into fm_backend_validate_task_endpoint's zellij and cmux blocks, matching herdr's self-repair-or-refuse structure. orca is left unchanged (refusing unconditionally), documented inline: no verified live-identity-check primitive exists to prove a recorded Orca terminal/worktree still belongs to a task. - tests/fm-teardown-endpoint-safety.test.sh: add test_zellij_legacy_meta_self_repair_and_recycled_pane_refusal and test_cmux_legacy_meta_self_repair_and_recycled_pane_refusal, mirroring the existing herdr test's self-repair and recycled-pane-refuses-without-mutation assertions. Squash-merge landed-work detection and herdr self-repair were already implemented and tested on main (commit ba2a480, PR #20); this change covers the remaining zellij/cmux gap. Full fm-teardown.test.sh suite: 49/49 pass, no regressions. bin/fm-lint.sh: clean.
📝 WalkthroughWalkthroughThe change adds live identity checks for legacy Zellij and cmux endpoints, replaces Claude account login seeding with validated Keychain setup tokens, and allowlists exact durable schedule-management tools. Tests and documentation cover the new behavior. ChangesEndpoint identity verification
Claude setup-token authentication
Schedule-tool allowlist
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant fm_backend
participant Backend_adapter
participant Live_endpoint
participant Endpoint_metadata
fm_backend->>Backend_adapter: verify legacy endpoint
Backend_adapter->>Live_endpoint: query current ownership and label
Live_endpoint-->>Backend_adapter: return live identity
Backend_adapter-->>fm_backend: return verification result
fm_backend->>Endpoint_metadata: repair binding when verified
sequenceDiagram
participant claude_account
participant macOS_Keychain
participant Claude_Code
claude_account->>macOS_Keychain: read account setup token
macOS_Keychain-->>claude_account: return validated token
claude_account->>Claude_Code: export CLAUDE_CODE_OAUTH_TOKEN
claude_account->>Claude_Code: launch with account-local configuration
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
bin/fm-subagent-pretool-check.sh (1)
81-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove reusable policy rationale out of the shell comments.
The changed comments duplicate task-specific policy knowledge in the implementation and tests. Keep only the local invariant that the exemption contains exact names and that near matches remain denied. Move the
robots-sy7mrationale,/scheduledependency, and delegation-policy explanation to committedAGENTS.mdor authoritative documentation.
bin/fm-subagent-pretool-check.sh#L81-L96: shorten the rationale aroundSCHEDULE_MGMT_TOOLS.tests/fm-subagent-pretool-check.test.sh#L44-L62: keep the fixtures and a concise contract comment.tests/fm-subagent-pretool-check.test.sh#L103-L105: keep the exclusion comment concise.tests/fm-subagent-pretool-check.test.sh#L159-L192: keep the assertions and move reusable rationale out of the tests.As per coding guidelines: Keep reusable project knowledge in the project's committed
AGENTS.md, fleet-local facts indata/learnings.md, captain preferences in the appropriate captain file, and task findings with the task or scout report.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@bin/fm-subagent-pretool-check.sh` around lines 81 - 96, Shorten the comments around SCHEDULE_MGMT_TOOLS in bin/fm-subagent-pretool-check.sh#81-96 to state only the exact-name exemption and near-match denial invariant; move the robots-sy7m rationale, /schedule dependency, and delegation-policy explanation to committed AGENTS.md or authoritative documentation. In tests/fm-subagent-pretool-check.test.sh#44-62, `#103-105`, and `#159-192`, retain the fixtures, assertions, and concise contract comments while removing duplicated reusable policy rationale.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@bin/claude-account.sh`:
- Around line 131-139: Remove the unconditional enableAllProjectMcpServers
assignment from the account bootstrap in bin/claude-account.sh, preserving
Claude Code’s default per-server approval unless an explicit captain opt-in
targets a known project/server. Update docs/configuration.md and the account
bootstrap tests to document and verify the replacement behavior.
In `@bin/fm-backend.sh`:
- Around line 500-502: Preserve metadata record boundaries when repairing
bindings: in bin/fm-backend.sh lines 500-502, update the Zellij repair path
around fm_backend_zellij_pane_verifies_task to append endpoint_task_id through a
format-aware metadata writer that inserts a missing final LF; apply the same
writer to the cmux repair path at lines 554-556. In
tests/fm-teardown-endpoint-safety.test.sh lines 215-236 and 264-287, add
no-final-LF Zellij and cmux fixtures and assert each repaired binding is
standalone without corrupting the preceding field.
In `@docs/configuration.md`:
- Around line 282-284: Update the fenced code block surrounding the security
command to declare the sh language, changing the opening fence from unlabeled to
sh while preserving the command and closing fence.
In `@tests/claude-account.test.sh`:
- Around line 162-165: Update the fixture assertion in the account configuration
test to search for the actual shared marker written by the fixture, “shared” or
“leak,” instead of “acct1-secret.” Keep the symlink assertion unchanged and
ensure copying the shared .claude.json into the account configuration causes the
test to fail.
---
Nitpick comments:
In `@bin/fm-subagent-pretool-check.sh`:
- Around line 81-96: Shorten the comments around SCHEDULE_MGMT_TOOLS in
bin/fm-subagent-pretool-check.sh#81-96 to state only the exact-name exemption
and near-match denial invariant; move the robots-sy7m rationale, /schedule
dependency, and delegation-policy explanation to committed AGENTS.md or
authoritative documentation. In tests/fm-subagent-pretool-check.test.sh#44-62,
`#103-105`, and `#159-192`, retain the fixtures, assertions, and concise contract
comments while removing duplicated reusable policy rationale.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 29ccd467-885c-4178-b28b-7d875dd4b027
📒 Files selected for processing (9)
bin/backends/cmux.shbin/backends/zellij.shbin/claude-account.shbin/fm-backend.shbin/fm-subagent-pretool-check.shdocs/configuration.mdtests/claude-account.test.shtests/fm-subagent-pretool-check.test.shtests/fm-teardown-endpoint-safety.test.sh
| # Auto-approve project-scoped (.mcp.json) MCP servers so a fresh account home | ||
| # is not dropped into the per-server "New MCP server found" prompt on first | ||
| # encounter. .claude.json is per-account (never symlinked, to avoid OAuth/ | ||
| # project-state leak), so an account never inherits the primary's MCP approvals | ||
| # - this global flag is the durable equivalent of choosing "use this and all | ||
| # future MCP servers in this project". | ||
| if not data.get("enableAllProjectMcpServers"): | ||
| data["enableAllProjectMcpServers"] = True | ||
| changed = True |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)(bin/claude-account\.sh|tests/fm-spawn-account\.test\.sh|docs/configuration\.md|$\.claude\.json|\.mcp\.json)$' || true
echo "== outline =="
ast-grep outline bin/claude-account.sh || true
echo "== relevant source lines =="
cat -n bin/claude-account.sh | sed -n '1,220p'
echo "== docs / config mentions =="
if [ -f docs/configuration.md ]; then rg -n "enableAllProjectMcpServers|enable.*Mcp|project.*mcp|\.mcp\.json" docs/configuration.md || true; fi
echo "== tests/mentions =="
rg -n "enableAllProjectMcpServers|enable.*Mcp|project.*mcp|\.mcp\.json|claude-account|CLAUDE_TRUST_DIR" bin tests docs -S || true
echo "== repo root config files =="
find . -maxdepth 3 \( -name '.claude.json' -o -name '.mcp.json' -o -name 'claude.json' -o -name 'mcp.json' \) -print 2>/dev/null || trueRepository: trillium/firstmate
Length of output: 16134
🌐 Web query:
Claude Code MCP project .mcp.json command enableAllProjectMcpServers documentation
💡 Result:
In Claude Code, enableAllProjectMcpServers is a configuration setting used to automatically approve and enable all MCP servers defined in a project's.mcp.json file, bypassing the individual per-server prompt [1][2][3]. Key points regarding its usage and limitations: 1. Purpose and Usage: By setting "enableAllProjectMcpServers": true in your Claude Code settings (typically in ~/.claude/settings.json), you authorize all project-scoped servers to run without needing to manually approve them each time [2][3]. 2. Important Security Constraint: As of Claude Code v2.1.196, this setting is ignored if the workspace folder is considered "untrusted" [4][1]. Even if "enableAllProjectMcpServers": true is committed to a project's.claude/settings.json file, Claude Code will not honor it until you have explicitly trusted the workspace by running the claude command in that folder and accepting the trust dialog [4][1]. This prevents a potential security risk where a cloned, untrusted repository could automatically execute its own configured servers [4][1]. 3. Recommended Alternative: For better security control, it is often recommended to use the enabledMcpjsonServers setting instead [1]. This allows you to explicitly list only the specific servers you trust by name, rather than using a blanket true setting that enables every server defined in the.mcp.json file [1]. 4. Troubleshooting: If your.mcp.json servers are stuck at "Pending approval," it is frequently because the workspace trust gate has not been cleared [4][1]. Additionally, note that entries in disabledMcpjsonServers will override enableAllProjectMcpServers, meaning a server explicitly disabled will not run regardless of your global "enable all" setting [1].
Citations:
- 1: https://startdebugging.net/2026/07/fix-mcp-json-servers-never-start-because-the-workspace-is-untrusted/
- 2: https://agentway.dev/en/claudecode/mcp
- 3: https://github.com/lamchakchan/claude-workspace/blob/main/docs/MCP-CONFIGS.md
- 4: https://code.claude.com/docs/en/mcp-servers.md
Security Misconfiguration (CWE-829): Inclusion of Functionality from Untrusted Control Sphere
Reachability: Internal
Do not persist enableAllProjectMcpServers=true on account bootstrap.
bin/claude-account.sh writes this global approval into $CLAUDE_CONFIG_DIR/.claude.json, and docs/configuration.md documents it as intentional. Add MCP approval requirements behind an explicit captain opt-in for a known project/server, or keep Claude Code’s default per-server approval. Update docs/configuration.md and the account bootstrap tests with the replacement behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bin/claude-account.sh` around lines 131 - 139, Remove the unconditional
enableAllProjectMcpServers assignment from the account bootstrap in
bin/claude-account.sh, preserving Claude Code’s default per-server approval
unless an explicit captain opt-in targets a known project/server. Update
docs/configuration.md and the account bootstrap tests to document and verify the
replacement behavior.
| if fm_backend_zellij_pane_verifies_task "$recorded_session" "$tab" "$pane" "$id" 2>/dev/null; then | ||
| echo "zellij endpoint self-repair: appending endpoint_task_id=$id to metadata for legacy task $id" >&2 | ||
| printf 'endpoint_task_id=%s\n' "$id" >> "$meta" || return 1 |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Preserve metadata record boundaries during repair.
At Line 502 and Line 556, a metadata file accepted without a final LF causes endpoint_task_id to concatenate with its final field. The function then returns success without creating a binding and corrupts that field.
bin/fm-backend.sh#L500-L502: append the Zellij binding through a format-aware metadata writer.bin/fm-backend.sh#L554-L556: use the same writer for the cmux binding.tests/fm-teardown-endpoint-safety.test.sh#L215-L236: add a Zellij fixture with no final LF and assert a standalone binding.tests/fm-teardown-endpoint-safety.test.sh#L264-L287: add the equivalent cmux regression case.
Proposed fix
+fm_backend_append_endpoint_task_id() { # <meta-file> <task-id>
+ local meta=$1 id=$2 last_byte
+ last_byte=$(tail -c 1 "$meta") || return 1
+ [ -z "$last_byte" ] || printf '\n' >> "$meta" || return 1
+ printf 'endpoint_task_id=%s\n' "$id" >> "$meta"
+}
+
- printf 'endpoint_task_id=%s\n' "$id" >> "$meta" || return 1
+ fm_backend_append_endpoint_task_id "$meta" "$id" || return 1📍 Affects 2 files
bin/fm-backend.sh#L500-L502(this comment)bin/fm-backend.sh#L554-L556tests/fm-teardown-endpoint-safety.test.sh#L215-L236tests/fm-teardown-endpoint-safety.test.sh#L264-L287
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bin/fm-backend.sh` around lines 500 - 502, Preserve metadata record
boundaries when repairing bindings: in bin/fm-backend.sh lines 500-502, update
the Zellij repair path around fm_backend_zellij_pane_verifies_task to append
endpoint_task_id through a format-aware metadata writer that inserts a missing
final LF; apply the same writer to the cmux repair path at lines 554-556. In
tests/fm-teardown-endpoint-safety.test.sh lines 215-236 and 264-287, add
no-final-LF Zellij and cmux fixtures and assert each repaired binding is
standalone without corrupting the preceding field.
| ``` | ||
| CLAUDE_CONFIG_DIR=~/.claude-homes/account1/.claude claude /login | ||
| security add-generic-password -U -s "ccjuggler-acc<N>" -a "ccjuggler" -w "<sk-ant-oat01-… token>" | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Declare the fenced block language.
Line 282 starts an unlabeled fenced code block. Use sh to satisfy MD040.
Proposed fix
-```
+```sh
security add-generic-password -U -s "ccjuggler-acc<N>" -a "ccjuggler" -w "<sk-ant-oat01-… token>"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ``` | |
| CLAUDE_CONFIG_DIR=~/.claude-homes/account1/.claude claude /login | |
| security add-generic-password -U -s "ccjuggler-acc<N>" -a "ccjuggler" -w "<sk-ant-oat01-… token>" | |
| ``` |
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)
[warning] 282-282: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/configuration.md` around lines 282 - 284, Update the fenced code block
surrounding the security command to declare the sh language, changing the
opening fence from unlabeled to sh while preserving the command and closing
fence.
Source: Linters/SAST tools
| # .claude.json is pre-seeded inside CLAUDE_CONFIG_DIR (where current CC reads | ||
| # it); it must be a real per-account file, never a symlink into shared config. | ||
| [ ! -L "$home/.claude-homes/account1/.claude/.claude.json" ] || fail ".claude.json must never be a symlink" | ||
| assert_no_grep "acct1-secret" "$home/.claude-homes/account1/.claude/.claude.json" \ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Check the fixture value that can leak.
The fixture writes {"shared":"leak"} at Line 158, but Line 165 searches for acct1-secret. A regression that copies the shared .claude.json into the account configuration would still pass.
Proposed fix
- assert_no_grep "acct1-secret" "$home/.claude-homes/account1/.claude/.claude.json" \
+ assert_no_grep '"shared":"leak"' "$home/.claude-homes/account1/.claude/.claude.json" \📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # .claude.json is pre-seeded inside CLAUDE_CONFIG_DIR (where current CC reads | |
| # it); it must be a real per-account file, never a symlink into shared config. | |
| [ ! -L "$home/.claude-homes/account1/.claude/.claude.json" ] || fail ".claude.json must never be a symlink" | |
| assert_no_grep "acct1-secret" "$home/.claude-homes/account1/.claude/.claude.json" \ | |
| # .claude.json is pre-seeded inside CLAUDE_CONFIG_DIR (where current CC reads | |
| # it); it must be a real per-account file, never a symlink into shared config. | |
| [ ! -L "$home/.claude-homes/account1/.claude/.claude.json" ] || fail ".claude.json must never be a symlink" | |
| assert_no_grep '"shared":"leak"' "$home/.claude-homes/account1/.claude/.claude.json" \ |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/claude-account.test.sh` around lines 162 - 165, Update the fixture
assertion in the account configuration test to search for the actual shared
marker written by the fixture, “shared” or “leak,” instead of “acct1-secret.”
Keep the symlink assertion unchanged and ensure copying the shared .claude.json
into the account configuration causes the test to fail.
…ld refuse, not validate
…repair in zellij and cmux backends
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/cmux-backend.md`:
- Around line 113-123: Remove the duplicated endpoint metadata schema and UUID
identity rules from docs/cmux-backend.md lines 113-123, preserving the legacy
self-repair paragraph and optionally linking to the existing authoritative
section. Apply the same cleanup to the duplicated Zellij schema and
pane-identity rules in docs/zellij-backend.md lines 90-102, with no changes to
the legacy self-repair paragraph.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 3413950b-86ff-458d-b9d7-ded8b7ea88c1
📒 Files selected for processing (6)
docs/cmux-backend.mddocs/configuration.mddocs/herdr-backend.mddocs/subagent-guard.mddocs/zellij-backend.mdtests/fm-teardown-endpoint-safety.test.sh
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/fm-teardown-endpoint-safety.test.sh
| ## Endpoint metadata | ||
|
|
||
| ```text | ||
| backend=cmux | ||
| window=<workspace-uuid>:<surface-uuid> | ||
| cmux_workspace_id=<workspace-uuid> | ||
| cmux_surface_id=<surface-uuid> | ||
| ``` | ||
|
|
||
| The UUID pair is the active endpoint authority within one app run. | ||
| Workspace UUIDs are not stable across an app relaunch, so recovery searches by the scoped title and then resolves the current surface id. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Keep one authoritative endpoint-metadata schema.
The new sections duplicate endpoint fields and identity rules already documented earlier in each file. Remove the repeated schema and rules, or replace them with a short link to the existing section. Keep the legacy self-repair paragraph.
docs/cmux-backend.md#L113-L123: remove the duplicate cmux schema and UUID rules.docs/zellij-backend.md#L90-L102: remove the duplicate Zellij schema and pane-identity rules.
📍 Affects 2 files
docs/cmux-backend.md#L113-L123(this comment)docs/zellij-backend.md#L90-L102
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/cmux-backend.md` around lines 113 - 123, Remove the duplicated endpoint
metadata schema and UUID identity rules from docs/cmux-backend.md lines 113-123,
preserving the legacy self-repair paragraph and optionally linking to the
existing authoritative section. Apply the same cleanup to the duplicated Zellij
schema and pane-identity rules in docs/zellij-backend.md lines 90-102, with no
changes to the legacy self-repair paragraph.
Source: Coding guidelines
Intent
Extend the herdr legacy-metadata self-repair pattern (PR #20, commit ba2a480) in bin/fm-backend.sh's fm_backend_validate_task_endpoint to the zellij and cmux backends: when endpoint_task_id is absent from legacy meta, re-derive live identity (pane/tab ownership and label match for zellij; surface/workspace membership and title match for cmux) and self-repair the binding only when it provably still matches the task, otherwise refuse. orca is intentionally left refusing unconditionally since it has no verified live-identity-check primitive. Adds fm_backend_zellij_pane_verifies_task, fm_backend_cmux_workspace_matches_label, and fm_backend_cmux_surface_verifies_task, plus matching tests in tests/fm-teardown-endpoint-safety.test.sh.
What Changed
fm_backend_validate_task_endpointto support legacy-metadata self-repair in zellij and cmux backends, detecting missing endpoint task IDs and re-deriving live identity to repair bindings only when they provably still match the original taskclaude-account.sh, and exemptedCronCreate/CronDeletefrom fm-subagent guard for durable schedule managementRisk Assessment
✅ Low: The implementation correctly extends the herdr self-repair pattern to zellij and cmux backends with proper defensive checks, clear error messages, and comprehensive test coverage including both success and failure scenarios.
Testing
Ran the full test suite for endpoint safety validation. All 11 tests pass, including three critical new tests: (1) zellij legacy metadata self-repair when live pane/tab still matches the task, and refusal without mutation when tab is recycled; (2) cmux legacy metadata self-repair when live workspace/surface still matches the task, and refusal without mutation when surface is recycled; (3) orca validation continues to refuse unconditionally with proper documentation. The three new functions (fm_backend_zellij_pane_verifies_task, fm_backend_cmux_workspace_matches_label, fm_backend_cmux_surface_verifies_task) are correctly implemented and invoked in the validation flow.
Evidence: Full test output
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
⏭️ **Rebase** - skipped
Step was skipped.
🔧 **Review** - 2 issues found → auto-fixed ✅
tests/fm-teardown-endpoint-safety.test.sh:234- Test error message says 'should validate' when the test expects validation to fail. The message is confusing because the test checks that rc != 0 (expects refusal), but the error message text suggests success should occur.tests/fm-teardown-endpoint-safety.test.sh:285- Same as line 234: test error message says 'should validate' when the expected behavior is validation failure. This will confuse developers reading the test or debugging failures.🔧 Fix: Fix test error messages: recycled resources should refuse, not validate
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bash tests/fm-teardown-endpoint-safety.test.sh✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation